import { isExist, isString, isFunction } from './object.utils'
import { nodeListToArray } from './array.utils'

export const EVENTS = {
  MOUSE_ENTER: 'mouseenter',
  MOUSE_LEAVE: 'mouseleave',
  MOUSE_DOWN: 'mousedown',
  MOUSE_UP: 'mouseup',
  FOCUS: 'focus',
  BLUR: 'blur',
  CLICK: 'click',
  INPUT: 'input',
  KEY_DOWN: 'keydown',
  KEY_UP: 'keyup',
  KEY_PRESS: 'keypress',
  RESIZE: 'resize',
  SCROLL: 'scroll',
  TOUCH_START: 'touchstart',
  TOUCH_END: 'touchend'
}

export const TRIGGERS = {
  CLICK: 'click',
  HOVER: 'hover',
  FOCUS: 'focus',
  HOVER_FOCUS: 'hover-focus',
  OUTSIDE_CLICK: 'outside-click',
  MANUAL: 'manual'
}

export const PLACEMENTS = {
  TOP: 'top',
  RIGHT: 'right',
  BOTTOM: 'bottom',
  LEFT: 'left'
}

export function isIE11 () {
  /* istanbul ignore next */
  return !!window.MSInputMethodContext && !!document.documentMode
}

export function isIE10 () {
  return window.navigator.appVersion.indexOf('MSIE 10') !== -1
}

export function getComputedStyle (el) {
  return window.getComputedStyle(el)
}

export function getViewportSize () {
  /* istanbul ignore next */
  const width = Math.max(document.documentElement.clientWidth, window.innerWidth) || 0
  /* istanbul ignore next */
  const height = Math.max(document.documentElement.clientHeight, window.innerHeight) || 0
  return { width, height }
}

let scrollbarWidth = null
let savedScreenSize = null

export function getScrollbarWidth (recalculate = false) {
  const screenSize = getViewportSize()
  // return directly when already calculated & not force recalculate & screen size not changed
  if (scrollbarWidth !== null && !recalculate &&
    screenSize.height === savedScreenSize.height && screenSize.width === savedScreenSize.width) {
    return scrollbarWidth
  }
  /* istanbul ignore next */
  if (document.readyState === 'loading') {
    return null
  }
  const div1 = document.createElement('div')
  const div2 = document.createElement('div')
  div1.style.width = div2.style.width = div1.style.height = div2.style.height = '100px'
  div1.style.overflow = 'scroll'
  div2.style.overflow = 'hidden'
  document.body.appendChild(div1)
  document.body.appendChild(div2)
  scrollbarWidth = Math.abs(div1.scrollHeight - div2.scrollHeight)
  document.body.removeChild(div1)
  document.body.removeChild(div2)
  // save new screen size
  savedScreenSize = screenSize
  return scrollbarWidth
}

export function on (element, event, handler) {
  /* istanbul ignore next */
  element.addEventListener(event, handler)
}

export function off (element, event, handler) {
  /* istanbul ignore next */
  element.removeEventListener(event, handler)
}

export function isElement (el) {
  return el && el.nodeType === Node.ELEMENT_NODE
}

export function removeFromDom (el) {
  isElement(el) && isElement(el.parentNode) && el.parentNode.removeChild(el)
}

export function ensureElementMatchesFunction () {
  /* istanbul ignore next */
  if (!Element.prototype.matches) {
    Element.prototype.matches =
      Element.prototype.matchesSelector ||
      Element.prototype.mozMatchesSelector ||
      Element.prototype.msMatchesSelector ||
      Element.prototype.oMatchesSelector ||
      Element.prototype.webkitMatchesSelector ||
      function (s) {
        const matches = (this.document || this.ownerDocument).querySelectorAll(s)
        let i = matches.length
        // eslint-disable-next-line no-empty
        while (--i >= 0 && matches.item(i) !== this) {}
        return i > -1
      }
  }
}

export function addClass (el, className) {
  if (!isElement(el)) {
    return
  }
  if (el.className) {
    const classes = el.className.split(' ')
    if (classes.indexOf(className) < 0) {
      classes.push(className)
      el.className = classes.join(' ')
    }
  } else {
    el.className = className
  }
}

export function removeClass (el, className) {
  if (!isElement(el)) {
    return
  }
  if (el.className) {
    const classes = el.className.split(' ')
    const newClasses = []
    for (let i = 0, l = classes.length; i < l; i++) {
      if (classes[i] !== className) {
        newClasses.push(classes[i])
      }
    }
    el.className = newClasses.join(' ')
  }
}

export function hasClass (el, className) {
  if (!isElement(el)) {
    return false
  }
  const classes = el.className.split(' ')
  for (let i = 0, l = classes.length; i < l; i++) {
    if (classes[i] === className) {
      return true
    }
  }
  return false
}

export function setDropdownPosition (dropdown, trigger, options = {}) {
  const doc = document.documentElement
  const containerScrollLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0)
  const containerScrollTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
  const rect = trigger.getBoundingClientRect()
  const dropdownRect = dropdown.getBoundingClientRect()
  dropdown.style.right = 'auto'
  dropdown.style.bottom = 'auto'
  if (options.menuRight) {
    dropdown.style.left = containerScrollLeft + rect.left + rect.width - dropdownRect.width + 'px'
  } else {
    dropdown.style.left = containerScrollLeft + rect.left + 'px'
  }
  if (options.dropup) {
    dropdown.style.top = containerScrollTop + rect.top - dropdownRect.height - 4 + 'px'
  } else {
    dropdown.style.top = containerScrollTop + rect.top + rect.height + 'px'
  }
}

export function isAvailableAtPosition (trigger, popup, placement) {
  const triggerRect = trigger.getBoundingClientRect()
  const popupRect = popup.getBoundingClientRect()
  const viewPortSize = getViewportSize()
  let top = true
  let right = true
  let bottom = true
  let left = true
  switch (placement) {
    case PLACEMENTS.TOP:
      top = triggerRect.top >= popupRect.height
      left = triggerRect.left + triggerRect.width / 2 >= popupRect.width / 2
      right = triggerRect.right - triggerRect.width / 2 + popupRect.width / 2 <= viewPortSize.width
      break
    case PLACEMENTS.BOTTOM:
      bottom = triggerRect.bottom + popupRect.height <= viewPortSize.height
      left = triggerRect.left + triggerRect.width / 2 >= popupRect.width / 2
      right = triggerRect.right - triggerRect.width / 2 + popupRect.width / 2 <= viewPortSize.width
      break
    case PLACEMENTS.RIGHT:
      right = triggerRect.right + popupRect.width <= viewPortSize.width
      top = triggerRect.top + triggerRect.height / 2 >= popupRect.height / 2
      bottom = triggerRect.bottom - triggerRect.height / 2 + popupRect.height / 2 <= viewPortSize.height
      break
    case PLACEMENTS.LEFT:
      left = triggerRect.left >= popupRect.width
      top = triggerRect.top + triggerRect.height / 2 >= popupRect.height / 2
      bottom = triggerRect.bottom - triggerRect.height / 2 + popupRect.height / 2 <= viewPortSize.height
      break
  }
  return top && right && bottom && left
}

export function setTooltipPosition (tooltip, trigger, placement, auto, appendTo, positionBy, viewport) {
  if (!isElement(tooltip) || !isElement(trigger)) {
    return
  }
  const isPopover = tooltip && tooltip.className && tooltip.className.indexOf('popover') >= 0
  let containerScrollTop
  let containerScrollLeft
  if (!isExist(appendTo) || appendTo === 'body' || positionBy === 'body') {
    const doc = document.documentElement
    containerScrollLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0)
    containerScrollTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
  } else {
    const container = getElementBySelectorOrRef(positionBy || appendTo)
    containerScrollLeft = container.scrollLeft
    containerScrollTop = container.scrollTop
  }
  // auto adjust placement
  if (auto) {
    // Try: right -> bottom -> left -> top
    // Cause the default placement is top
    const placements = [PLACEMENTS.RIGHT, PLACEMENTS.BOTTOM, PLACEMENTS.LEFT, PLACEMENTS.TOP]
    // The class switch helper function
    const changePlacementClass = (placement) => {
      // console.log(placement)
      placements.forEach(placement => {
        removeClass(tooltip, placement)
      })
      addClass(tooltip, placement)
    }
    // No need to adjust if the default placement fits
    if (!isAvailableAtPosition(trigger, tooltip, placement)) {
      for (let i = 0, l = placements.length; i < l; i++) {
        // Re-assign placement class
        changePlacementClass(placements[i])
        // Break if new placement fits
        if (isAvailableAtPosition(trigger, tooltip, placements[i])) {
          placement = placements[i]
          break
        }
      }
      changePlacementClass(placement)
    }
  }
  // fix left and top for tooltip
  const rect = trigger.getBoundingClientRect()
  const tooltipRect = tooltip.getBoundingClientRect()
  let top
  let left
  if (placement === PLACEMENTS.BOTTOM) {
    top = containerScrollTop + rect.top + rect.height
    left = containerScrollLeft + rect.left + rect.width / 2 - tooltipRect.width / 2
  } else if (placement === PLACEMENTS.LEFT) {
    top = containerScrollTop + rect.top + rect.height / 2 - tooltipRect.height / 2
    left = containerScrollLeft + rect.left - tooltipRect.width
  } else if (placement === PLACEMENTS.RIGHT) {
    top = containerScrollTop + rect.top + rect.height / 2 - tooltipRect.height / 2
    // https://github.com/uiv-lib/uiv/issues/272
    // add 1px to fix above issue
    left = containerScrollLeft + rect.left + rect.width + 1
  } else {
    top = containerScrollTop + rect.top - tooltipRect.height
    left = containerScrollLeft + rect.left + rect.width / 2 - tooltipRect.width / 2
  }
  let viewportEl
  // viewport option
  if (isString(viewport)) {
    viewportEl = document.querySelector(viewport)
  } else if (isFunction(viewport)) {
    viewportEl = viewport(trigger)
  }
  if (isElement(viewportEl)) {
    const popoverFix = isPopover ? 11 : 0
    const viewportReact = viewportEl.getBoundingClientRect()
    const viewportTop = containerScrollTop + viewportReact.top
    const viewportLeft = containerScrollLeft + viewportReact.left
    const viewportBottom = viewportTop + viewportReact.height
    const viewportRight = viewportLeft + viewportReact.width
    // fix top
    if (top < viewportTop) {
      top = viewportTop
    } else if (top + tooltipRect.height > viewportBottom) {
      top = viewportBottom - tooltipRect.height
    }
    // fix left
    if (left < viewportLeft) {
      left = viewportLeft
    } else if (left + tooltipRect.width > viewportRight) {
      left = viewportRight - tooltipRect.width
    }
    // fix for popover pointer
    if (placement === PLACEMENTS.BOTTOM) {
      top -= popoverFix
    } else if (placement === PLACEMENTS.LEFT) {
      left += popoverFix
    } else if (placement === PLACEMENTS.RIGHT) {
      left -= popoverFix
    } else {
      top += popoverFix
    }
  }
  // set position finally
  tooltip.style.top = `${top}px`
  tooltip.style.left = `${left}px`
}

export function hasScrollbar (el) {
  const SCROLL = 'scroll'
  const hasVScroll = el.scrollHeight > el.clientHeight
  const style = getComputedStyle(el)
  return hasVScroll || style.overflow === SCROLL || style.overflowY === SCROLL
}

export function toggleBodyOverflow (enable) {
  const MODAL_OPEN = 'modal-open'
  const FIXED_CONTENT = '.navbar-fixed-top, .navbar-fixed-bottom'
  const body = document.body
  if (enable) {
    removeClass(body, MODAL_OPEN)
    body.style.paddingRight = null
    nodeListToArray(document.querySelectorAll(FIXED_CONTENT)).forEach(node => {
      node.style.paddingRight = null
    })
  } else {
    const browsersWithFloatingScrollbar = isIE10() || isIE11()
    const documentHasScrollbar = hasScrollbar(document.documentElement) || hasScrollbar(document.body)
    if (documentHasScrollbar && !browsersWithFloatingScrollbar) {
      const scrollbarWidth = getScrollbarWidth()
      body.style.paddingRight = `${scrollbarWidth}px`
      nodeListToArray(document.querySelectorAll(FIXED_CONTENT)).forEach(node => {
        node.style.paddingRight = `${scrollbarWidth}px`
      })
    }
    addClass(body, MODAL_OPEN)
  }
}

export function getClosest (el, selector) {
  ensureElementMatchesFunction()
  let parent
  let _el = el
  while (_el) {
    parent = _el.parentElement
    if (parent && parent.matches(selector)) {
      return parent
    }
    _el = parent
  }
  return null
}

export function getParents (el, selector, until = null) {
  ensureElementMatchesFunction()
  const parents = []
  let parent = el.parentElement
  while (parent) {
    if (parent.matches(selector)) {
      parents.push(parent)
    } else if (until && (until === parent || parent.matches(until))) {
      break
    }
    parent = parent.parentElement
  }
  return parents
}

export function focus (el) {
  if (!isElement(el)) {
    return
  }
  el.getAttribute('tabindex') ? null : el.setAttribute('tabindex', '-1')
  el.focus()
}

const MODAL_BACKDROP = 'modal-backdrop'

export function getOpenModals () {
  return document.querySelectorAll(`.${MODAL_BACKDROP}`)
}

export function getOpenModalNum () {
  return getOpenModals().length
}

export function getElementBySelectorOrRef (q) {
  if (isString(q)) { // is selector
    return document.querySelector(q)
  } else if (isElement(q)) { // is element
    return q
  } else if (isElement(q.$el)) { // is component
    return q.$el
  } else {
    return null
  }
}
